#ifndef _MOE_STATISTICS_
#define _MOE_STATISTICS_

#include "engine/netstream.hh"
#include "engine/Observer.hh"
#include "engine/Population.hh"
#include <iostream>

using namespace std;
/**The statistics of the algorithm
  *it is strongly recommended to initialize this class with a NetStream
  *in a different group from the Migration group, in order to do not get into
  *troubles with the concurrency.
  *Statistics values are number of generations, count of evaluations, hit's
  *in the evaluations cache, percent of differents elements in the
  *population and the pareto front
  */
class Statistics : public Observer {

private:

    NetStream *stream;
    Solution *pivot;
    int synchronizePeriod;


    int generation;
    int evaluations;
    int hits;
    int populationSize;
    double distincts;
    int end; //end of the process
    Population *paretoFront;


public:

    Statistics(NetStream *stream, Solution *pivot) : Observer() {
        this->stream = stream;
        this->pivot = pivot;
        paretoFront = NULL;
        reset();
    }

    void setSynchronizePeriod(int synchronizePeriod) {
        this->synchronizePeriod = synchronizePeriod;
    }

    int getSynchronizePeriod() {
        return synchronizePeriod;
    }

    void setNetStream(NetStream *stream) {
        this->stream = stream;
    }

    bool ended() {
        return end;
    }


    virtual void reset() {
        if (paretoFront != NULL) delete paretoFront;

        paretoFront = new Population();

        generation = -1;

        evaluations = 0;

        hits = 0;

        populationSize = 0;

        distincts = 0;

        end = false;

    }

    virtual ~Statistics() {
        if (paretoFront != NULL) delete paretoFront;

        delete pivot;
    }

    virtual void notify(const void *value) {
        MOEA *moea = (MOEA*) value;

        if (moea->stopCondition() ||
            (moea->getGenerations() != 0 && ((moea->getGenerations() % synchronizePeriod)) == 0)) {

            update((MOEA*)value);
        }
    }

    virtual void update(MOEA *moea) {

        if (!end) {

            ArrayList<Solution>* newParetoFront = moea->getCurrent()->getParetoFront();

            newParetoFront->setOwner(false);

            paretoFront->replace(newParetoFront);

            delete newParetoFront;


            end = moea->stopCondition();
            generation = moea->getGenerations();
            evaluations = Solution::getEvaluations();
            hits = Solution::getHits();

            countDistincts(moea->getCurrent());
            populationSize = moea->getCurrent()->length();

            if (stream != NULL && stream->pnumber() > 1) {

                if (moea->stopCondition() || (moea->getGenerations() != 0 && ((moea->getGenerations() % synchronizePeriod)) == 0)) {
                    (*stream) << barrier;

                    (*stream) << set_target(0);
                    (*stream) << pack_begin;
                    send();
                    (*stream) << pack_end;
                }
            } else {
                cout << endl;
                cout << "---------------------------------------------------" << endl;
                cout << "[partial results]" << endl;
                send(cout);
                cout << "---------------------------------------------------" << endl;
                cout << endl;

            }
        }
    }


    virtual void update(Statistics *statistics) {

        int islands = stream->pnumber() - 1;
        generation = statistics->generation;
        evaluations = evaluations + statistics->evaluations;
        hits = hits + statistics->hits;
        distincts = distincts + statistics->distincts / islands;
        populationSize = statistics->populationSize;

        synchronizeParetoFront(statistics);
    }



    virtual Statistics* receive() {
        Statistics* statistics = new Statistics(stream, pivot->copy());
        int paretoSize = 0;

        (*stream) >> statistics->end;

        (*stream) >> statistics->generation;
        (*stream) >> statistics->evaluations;
        (*stream) >> statistics->hits;
        (*stream) >> statistics->distincts;
        (*stream) >> statistics->populationSize;


        (*stream) >> paretoSize;
        ArrayList<Solution> *list = new ArrayList<Solution>(false);


        for (int i = 0; i < paretoSize; i++) {
            Solution *solution = pivot->copy();
            solution->receive((*stream));
            list->add(solution);
        }


        statistics->paretoFront->replace(list);
        delete list;
        return statistics;
    }

    virtual void send() {

        (*stream) << end;

        (*stream) << generation;
        (*stream) << evaluations;
        (*stream) << hits;
        (*stream) << distincts;
        (*stream) << populationSize;

        (*stream) << paretoFront->length();

        for (int i = 0; i < paretoFront->length(); i++) paretoFront->get(i)->send((*stream));

    }

    virtual void send(ostream &out) {

        out << "[statistics]" << endl;
        out << "\tgeneration  " << generation << endl;
        out << "\tevaluations " << evaluations << endl;
        out << "\tcached      " << hits << endl;
        out << "\tdifferent solutions (average) " << distincts << endl;
        out << "\tpopulation size  (per island) " << populationSize << endl;
        out << "\tpareto front size " << paretoFront->length() << endl;
        out << endl;
    }

    virtual ArrayList<Solution> *getParetoFront() {
        return paretoFront->getAll();
    }


private:

    void synchronizeParetoFront(Statistics *statistics) {
        ArrayList<Solution> *newParetoFront = new ArrayList<Solution>(false);

        for (int i = 0; i < statistics->paretoFront->getAll()->length(); i++)
            newParetoFront->add(statistics->paretoFront->get(i)->copy());

        paretoFront->getAll()->addAll(newParetoFront);

        delete newParetoFront;

        newParetoFront = paretoFront->getParetoFront();

        paretoFront->replace(newParetoFront);

    }


    virtual void countDistincts(Population *population) {
        ArrayList<Solution> *all = population->getAll();
        distincts = population->length();
        bool* marks = new bool[population->length()];

        for (int i = 0; i < population->length(); i++) marks[i] = false;

        for (int i = 0; i < all->length(); i++) {
            if (!marks[i]) {
                Solution *solution = all->get(i);
                marks[i] = true;

                for (int j = 0; j < all->length(); j++) {
                    Solution *otherSolution = all->get(j);

                    if (!marks[j] && otherSolution->equals(solution)) {
                        distincts = distincts - 1;
                        marks[j] = true;
                    }
                }
            }
        }

        delete [] marks;
    }
};

#endif
